package xj.graph2d.shapes;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.util.DrawingUtilities;
import xj.graph2d.graph.Geometry;
import xj.util.gui.Point2DFloat;

public class PolyShapeAdapter 
  implements PolyShapeBase, DrawShapeConstants {

  public PolyShapeAdapter(DrawShape owner, boolean isPolygon) {
    this.owner = owner;
    this.isPolygon = isPolygon;
    this.x = 0;
    this.y = 0;
    points = new ArrayList<Point2D>();
  }

  public float getX() {
    return x;
  }

  public float getY() {
    return y;
  }

  public void setFirst(float x, float y) {
    if (debug) {
      System.out.println("PolyShapeAdapter.setFirst(): x=" + x + " y=" + y);
    }

    this.x = x;
    this.y = y;
  }

  public void setStart(float x, float y) {
    if (debug) {
      System.out.println("PolyShapeAdapter.setStart(): x=" + x + " y=" + y);
    }

    this.x = x;
    this.y = y;
    if (points.size() > 0) {
      points.clear();
    }
  }

  public void setFirstOnly(float x, float y) {
    if (debug) {
      System.out.println("PolyShapeAdapter.setFirstOnly(): x=" + x + " y=" + y);
    }

    float dx = x - this.x;
    float dy = y - this.y;
    this.x = x;
    this.y = y;
    if (points != null) {
      int n = points.size();
      for (int i = 0; i < n; i++) {
	Point2D p = points.get(i);
	p.setLocation(p.getX() - dx, p.getY() - dy);
      }
    }
  }

  public void addPoint() {
    int n = points.size();
    if (n > 0) {
      Point2D p = points.get(n - 1);
      if (p != null) {
	points.add(new Point2DFloat((float) p.getX(), (float) p.getY()));
      }
    } else {
      points.add(new Point2DFloat(0, 0));
    }
  }

  public void addPoint(float x, float y) {
    addPoint(x, y, true);
  }

  public void addPoint(float x, float y, boolean adj) {
    if (adj) {
      points.add(new Point2DFloat(x - this.x, y - this.y));
    } else {
      points.add(new Point2DFloat(x, y));
    }
    // polygon = null;
    isBoundValid = false;
  }

  public void moveStartOnly(float dx, float dy) {
    this.x += dx;
    this.y += dy;
    if (points != null) {
      int n = points.size();
      for (int i = 0; i < n; i++) {
	Point2D p = points.get(i);
	p.setLocation(p.getX() - dx, p.getY() - dy);
      }
    }
    // polygon = null;
    isBoundValid = false;
    calculateBounds();
  }

  public void moveVertex(int i, float dx, float dy) {
    if (i == 0) {
      // the anchor point
      // this.x += dx;
      // this.y += dy;
      moveStartOnly(dx, dy);
      /*
       * if (container != null) { container.componentMoved(this, dx, dy); }
       */
    } else if (i >= 1 && i <= points.size()) {
      Point2D p = points.get(i - 1);
      p.setLocation(p.getX() + dx, p.getY() + dy);
    }
    // polygon = null;
    isBoundValid = false;
    calculateBounds();
  }

  public void moveStartAdj(float dx, float dy) {
    int n = points.size();
    if (n > 0) {
      float x01 = this.x;
      float y01 = this.y;
      Point2D lp = points.get(n - 1);
      float x02 = this.x + (float) lp.getX();
      float y02 = this.y + (float) lp.getY();
      scaleFirstLast(x01 + dx, y01 + dy, x02, y02, null);
    }
  }

  public void moveLastAdj(float dx, float dy) {
    int n = points.size();
    if (n > 0) {
      float x01 = this.x;
      float y01 = this.y;
      Point2D lp = points.get(n - 1);
      float x02 = this.x + (float) lp.getX();
      float y02 = this.y + (float) lp.getY();
      scaleFirstLast(x01, y01, x02 + dx, y02 + dy, null);
    }
  }

  public void setPoint(int i, float x, float y) {
    if (i == 0) {
      // the anchor point
      if (debug) {
	System.out.println("PolyShapeAdapter.setPoint(): i=0 x=" + x + " y=" + y);
      }

      this.x = x;
      this.y = y;
    } else if (i >= 1 && i <= points.size()) {
      Point2D p = points.get(i - 1);
      // p.setLocation(x, y);
      p.setLocation(x - this.x, y - this.y);
    }
  }

  public Point2D getPoint(int i) {
    if (i >= 0 && i < points.size()) {
      return points.get(i);
    }
    return null;
  }

  public java.util.List<Point2D> getPoints() {
    return points;
  }

  public void addPoint(int i, float x, float y) {
    addPoint(i, x, y, true);
  }

  public void addPoint(int i, float x, float y, boolean adj) {
    if (i >= 0) {
      Point2D p;
      if (adj) {
	p = new Point2DFloat(x - this.x, y - this.y);
      } else {
	p = new Point2DFloat(x, y);
      }
      if (i < points.size()) {
	points.add(i, p);
      } else {
	points.add(p);
      }
      // polygon = null;
      isBoundValid = false;
    }
  }

  public void removePoint(int i) {
    if (i >= 0 && i < points.size()) {
      points.remove(i);
    }
    // polygon = null;
    isBoundValid = false;
  }

  public void setLast(float x, float y) {
    int n = points.size();

    if (debug) {
      System.out.println("PolyShapeAdapterImpl.setLast() before n=" + n);
    }

    if (n > 0) {
      Point2D p = points.get(n - 1);
      p.setLocation(x - this.x, y - this.y);
    } else {
      if (debug) {
	System.out.println("PolyShapeAdapter.setLast(): x=" + x + " y=" + y);
      }
      this.x = x;
      this.y = y;
    }
    // polygon = null;
    isBoundValid = false;

    if (debug) {
      System.out.println("PolyShapeAdapterImpl.setLast() after  n=" + points.size());
    }
  }

  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    setEndsAdj(x1, y1, x2, y2, null);
    isBoundValid = false;
    calculateBounds();
  }

  public void setEndsAdj(float x1, float y1, float x2, float y2,
			 Point2D[] extraPoints) {
    float xmin, ymin, xmax, ymax;
    if (owner instanceof PolyCurveShape) { 
      Rectangle2D r = owner.getBounds();
      xmin = (float) r.getX();
      ymin = (float) r.getY();
      xmax = (float) (r.getX() + r.getWidth());
      ymax = (float) (r.getY() + r.getHeight());
    } else { 
      xmin = xmax = this.x;
      ymin = ymax = this.y;
      if (points != null) {
	int n = points.size();
	for (int i = 1; i <= n; i++) {
	  Point2D p = points.get(i - 1);
	  if (p != null) {
	    float px = (float) p.getX() + this.x;
	    float py = (float) p.getY() + this.y;
	    if (px < xmin) {
	      xmin = px;
	    }
	    if (px > xmax) {
	      xmax = px;
	    }
	    if (py < ymin) {
	      ymin = py;
	    }
	    if (py > ymax) {
	      ymax = py;
	    }
	  }
	}
      }
    }

    float x0 = Math.min(x1, x2);
    float y0 = Math.min(y1, y2);
    float sx = 1;
    float sy = 1;
    if (x1 == x2 && y1 == y2) {
      sx = 0;
      sy = 0;
    } else {
      if (Math.abs(xmax - xmin) > 0.0001) {
	sx = Math.abs(x2 - x1) / (xmax - xmin);
      }
      if (Math.abs(ymax - ymin) > 0.0001) {
	sy = Math.abs(y2 - y1) / (ymax - ymin);
      }
    }

    float newx = x0 + (this.x - xmin) * sx;
    float newy = y0 + (this.y - ymin) * sy;
    if (points != null) {
      int n = points.size();
      for (int i = 1; i <= n; i++) {
	Point2D p = points.get(i - 1);
	if (p != null) {
	  float px = (float) p.getX() + this.x;
	  float py = (float) p.getY() + this.y;
	  px = x0 + (px - xmin) * sx;
	  py = y0 + (py - ymin) * sy;
	  p.setLocation(px - newx, py - newy);
	}
      }
    }

    if (extraPoints != null) {
      int n = extraPoints.length;
      for (int i = 0; i < n; i++) {
	Point2D p = extraPoints[i];
	if (p != null) {
	  float px = (float) p.getX();
	  float py = (float) p.getY();
	  px = x0 + (px - xmin) * sx;
	  py = y0 + (py - ymin) * sy;
	  p.setLocation(px, py);
	}
      }
    }

    this.x = newx;
    this.y = newy;

    if (debug) {
      System.out.println("PolyShapeAdapter.setEndsAdj(): x=" + x + " y=" + y);
      /*
       * try { throw new Exception(); } catch (Exception e) {
       * e.printStackTrace(); }
       */
    }

  }

  public void scale(float sx, float sy) {
    float xmin, ymin, xmax, ymax;
    xmin = xmax = this.x;
    ymin = ymax = this.y;
    if (points != null) {
      int n = points.size();
      for (int i = 1; i <= n; i++) {
	Point2D p = points.get(i - 1);
	if (p != null) {
	  float px = (float) p.getX() + this.x;
	  float py = (float) p.getY() + this.y;
	  if (px < xmin) {
	    xmin = px;
	  }
	  if (px > xmax) {
	    xmax = px;
	  }
	  if (py < ymin) {
	    ymin = py;
	  }
	  if (py > ymax) {
	    ymax = py;
	  }
	}
      }
    }

    float newx = xmin + (this.x - xmin) * sx;
    float newy = ymin + (this.y - ymin) * sy;
    if (points != null) {
      int n = points.size();
      for (int i = 1; i <= n; i++) {
	Point2D p = points.get(i - 1);
	if (p != null) {
	  float px = (float) p.getX() + this.x;
	  float py = (float) p.getY() + this.y;
	  px = xmin + (px - xmin) * sx;
	  py = ymin + (py - ymin) * sy;
	  p.setLocation(px - newx, py - newy);
	}
      }
    }
    this.x = newx;
    this.y = newy;

    isBoundValid = false;
    calculateBounds();

    if (debug) {
      System.out.println("PolyShapeAdapter.scale(): x=" + x + " y=" + y);
    }
  }

  public void scaleFirstLast(float x1, float y1, 
			     float x2, float y2) {
    scaleFirstLast(x1, y1, x2, y2, null, false, false);
  }

  public void scaleFirstLast(float x1, float y1, 
			     float x2, float y2,
			     boolean limit) {
    scaleFirstLast(x1, y1, x2, y2, null, limit, false);
  }

  public void scaleFirstLast(float x1, float y1, 
			     float x2, float y2,
			     boolean limit,
			     boolean linear) {
    scaleFirstLast(x1, y1, x2, y2, null, limit, linear);
  }

  public void scaleFirstLast(float x1, float y1, 
			     float x2, float y2,
			     Point2D[] extraPoints) {
    scaleFirstLast(x1, y1, x2, y2, extraPoints, false, false);
  }

  public void scaleFirstLast(float x1, float y1, 
			     float x2, float y2,
			     Point2D[] extraPoints,
			     boolean limit) {
    scaleFirstLast(x1, y1, x2, y2, extraPoints, false, false);
  }

  public void scaleFirstLast(float x1, float y1, 
			     float x2, float y2,
			     Point2D[] extraPoints, 
			     boolean limit,
			     boolean linear) {
    if (linear) { 
      scaleFirstLastLinear(x1, y1, x2, y2, extraPoints, limit);      
    } else { 
      scaleFirstLastAngular(x1, y1, x2, y2, extraPoints, limit);
    }

    if (debug) {
      System.out.println("PolyShapeAdapter.scaleFirstLast(): x1=" + x1 + " y1=" + y1 + 
			 " x2=" + x2 + " y2=" + y2);
    }
  }

  public void scaleFirstLastAngular(float x1, float y1, 
				    float x2, float y2,
				    Point2D[] extraPoints, 
				    boolean limit) {
    if (points != null) {
      int n = points.size();
      if (n > 0) {
	Point2D pfirst = new Point2DFloat(this.x, this.y);
	Point2D pn = points.get(n - 1);
	Point2D plast = new Point2DFloat(this.x + (float) pn.getX(), this.y + (float) pn.getY());
	Point2D q1 = new Point2DFloat(x1, y1);
	Point2D q2 = new Point2DFloat(x2, y2);

	for (int i = 1; i <= n; i++) {
	  Point2D p = points.get(i - 1);
	  if (p != null) {
	    if (i < n) { 
	      Point2D r1 = new Point2DFloat(this.x + (float) p.getX(), this.y + (float) p.getY());
	      Point2D r2 = Geometry.scaleRelativeToEndPoints(pfirst, plast, q1, q2, r1);
	      p.setLocation((float) r2.getX() - x1, (float) r2.getY() - y1);
	    } else { 
	      p.setLocation(x2 - x1, y2 - y1);
	    }
	  }
	}
      
	if (extraPoints != null) {
	  int ne = extraPoints.length;
	  for (int i = 0; i < ne; i++) {
	    Point2D p = extraPoints[i];
	    if (p != null) {
	      Point2D r1 = new Point2DFloat(this.x + (float) p.getX(), this.y + (float) p.getY());
	      Point2D r2 = Geometry.scaleRelativeToEndPoints(pfirst, plast, q1, q2, r1);
	      p.setLocation((float) r2.getX() - x1, (float) r2.getY() - y1);	      
	    }
	  }
	}
      }

      this.x = x1;
      this.y = y1;
      
      isBoundValid = false;
      calculateBounds();
      
      if (debug) {
	Point2D p = points.get(n - 1);
	System.out.println("PolyShapeAdapter.scaleFirstLastAngular(): x=" + x + " y=" + y + 
			   " last=(" + (p.getX() + x) + "," + (p.getY() + y) + ")" + 
			   (extraPoints != null ? ("extraPoints.length=" + extraPoints.length) : ""));
      }
    }
  }

  public void scaleFirstLastLinear(float x1, float y1, 
				   float x2, float y2,
				   Point2D[] extraPoints, 
				   boolean limit) {
    int n = points.size();
    if (n > 0) {
      // relative to (x1, y1)
      float minX = Math.min(0, x2 - x1);
      float maxX = Math.max(0, x2 - x1);
      float minY = Math.min(0, y2 - y1);
      float maxY = Math.max(0, y2 - y1);

      float x01 = this.x;
      float y01 = this.y;
      Point2D lp = points.get(n - 1);
      float x02 = (float) lp.getX();
      float y02 = (float) lp.getY();

      float sx = 1;
      float sy = 1;
      if (x1 == x2 && y1 == y2) {
	sx = 0;
	sy = 0;
      } else {
	if (x02 != 0) {
	  sx = Math.abs((x2 - x1) / x02);
	}
	if (y02 != 0) {
	  sy = Math.abs((y2 - y1) / y02);
	}
      }

      if (points != null) {
	for (int i = 1; i <= n; i++) {
	  Point2D p = points.get(i - 1);
	  if (p != null) {
	    if (i < n) { 
	      float px = (float) p.getX();
	      float py = (float) p.getY();
	      float px1 = px * sx;
	      float py1 = py * sy;
	      if (x02 == 0 && x2 - x1 != 0) {
		px1 = px + (x2 - x1) * i / n;
	      }
	      if (y02 == 0 && y2 - y1 != 0) {
		py1 = py + (y2 - y1) * i / n;
	      }
	      if (limit) {
		if (px1 >= minX && px1 <= maxX) {
		  px = px1;
		} else {
		  px = px * (sx >= 0 ? 1 : -1);
		}
		if (py1 >= minY && py1 <= maxY) {
		  py = py1;
		} else {
		  py = py * (sy >= 0 ? 1 : -1);
		}
	      } else {
		px = px1;
		py = py1;
	      }
	      p.setLocation(px, py);
	    } else { 
	      p.setLocation(x2 - x1, y2 - y1);
	    }
	  }
	}
      }

      if (extraPoints != null) {
	int ne = extraPoints.length;
	for (int i = 0; i < ne; i++) {
	  Point2D p = extraPoints[i];
	  if (p != null) {
	    float px = (float) p.getX();
	    float py = (float) p.getY();
	    float px1 = (px - x01) * sx;
	    float py1 = (py - y01) * sy;
	    if (x02 == 0 && x2 - x1 != 0) {
	      px1 = px + (x2 - x1) * i / n;
	    }
	    if (y02 == 0 && y2 - y1 != 0) {
	      py1 = py + (y2 - y1) * i / n;
	    }
	    if (limit) {
	      if (px1 >= minX && px1 <= maxX) {
		px = x1 + px1;
	      } else {
		px = x1 + (px - x01) * (sx >= 0 ? 1 : -1);
	      }
	      if (py1 >= minY && py1 <= maxY) {
		py = x1 + py1;
	      } else {
		py = y1 + (py - y01) * (sy >= 0 ? 1 : -1);
	      }
	    } else {
	      px = x1 + px1;
	      py = y1 + py1;
	    }
	    p.setLocation(px, py);
	  }
	}
      }
    }

    this.x = x1;
    this.y = y1;

    isBoundValid = false;
    calculateBounds();

    if (debug) {
      Point2D p = points.get(n - 1);
      System.out.println("PolyShapeAdapter.scaleFirstLastLinear(): x=" + x + " y=" + y + 
			 " last=(" + (p.getX() + x) + "," + (p.getY() + y) + ")" + 
			 (extraPoints != null ? ("extraPoints.length=" + extraPoints.length) : ""));
    }
  }

  public void scaleEnds(float x0, float y0, float sx, float sy,
			Point2D[] extraPoints) {

  }

  public void drawPolyLine(Graphics2D g2) {
    if (points != null) {
      int n = points.size();
      float prevx = x;
      float prevy = y;
      float curx, cury;
      for (int i = 0; i < n; i++) {
	Point2D p = points.get(i);
	if (p != null) {
	  curx = (float) p.getX() + this.x;
	  cury = (float) p.getY() + this.y;
	  g2.draw(new Line2D.Float(prevx, prevy, curx, cury));
	  prevx = curx;
	  prevy = cury;
	}
      }
    }
  }

  public Point2D getAnchorPoint() {
    return new Point2DFloat(x, y);
  }

  public Point2D getFirstPoint() {
    return new Point2DFloat(x, y);
  }

  public Point2D getLastPoint() {
    int n = points.size();
    if (n > 0) {
      Point2D p = points.get(n - 1);
      return new Point2DFloat(x + (float) p.getX(), y + (float) p.getY());
    }
    return null;
  }

  public Point2D getLast() {
    int n = points.size();
    if (n > 0) {
      return points.get(n - 1);
    }
    return null;
  }

  public int getNumberOfPoints() {
    return points.size() + 1;
  }

  public void move(float dx, float dy) {
    x += dx;
    y += dy;
    // polygon = null;
    isBoundValid = false;
    // super.move(dx, dy);
  }

  public void drawFrame(Graphics2D g2) {
    drawFrame(g2, false, false);
  }

  public void drawFrame(Graphics2D g2, boolean markFirst, boolean markLast) {
    int penSize = 1;
    DrawAttr attr = null;
    if (owner != null) {
      attr = owner.getDrawAttr();
    }
    if (attr != null) {
      penSize = attr.getPenSize();
    }
    int marksize =  DrawingUtilities.getMarkSize(penSize);
    float md = marksize / 2.0f;
    Color saveColor = g2.getColor();
    Stroke saveStroke = g2.getStroke();

    g2.setStroke(markStroke);
    if (isPolygon) {
      g2.setColor(defaultFrameColor);
    } else {
      if (markFirst) {
	g2.setColor(edgeSourceColor);
      } else {
	g2.setColor(defaultFrameColor);
      }
    }

    Shape m; 
    if (isPolygon) {
      m = new Rectangle2D.Float(this.x - md, this.y - md, marksize, marksize);
    } else { 
      m = new Ellipse2D.Float(this.x - md, this.y - md, marksize, marksize);
    }
    if (frameMarkFilled) { 
      g2.fill(m);
    } else { 
      g2.draw(m);
    }
    int n = points.size();
    float curx, cury;
    g2.setColor(defaultFrameColor);
    for (int i = 0; i < n; i++) {
      Point2D p = points.get(i);
      if (p != null) {
	curx = (float) p.getX() + this.x;
	cury = (float) p.getY() + this.y;
	if (i == n - 1 && markLast) {
	  g2.setColor(edgeDestinationColor);
	}
	if (isPolygon) {
	  m = new Rectangle2D.Float(curx - md, cury - md, marksize, marksize);
	} else { 
	  m = new Ellipse2D.Float(curx - md, cury - md, marksize, marksize);
	}
	if (frameMarkFilled) { 
	  g2.fill(m);
	} else { 
	  g2.draw(m);
	}
      }
    }

    g2.setColor(saveColor);
    g2.setStroke(saveStroke);
  }

  public int isInside(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isInside(px, py);
    } else {
      return PointOutside;
    }
  }

  public int isInside(float px, float py) {
    float d1 = d;
    int result = PointOutside;
    if (owner != null) {
      DrawAttr attr = owner.getDrawAttr();
      if (attr != null) {
	int penSize = attr.getPenSize();
	if (penSize > 1) {
	  d1 += (penSize / 2);
	}
      }
      Rectangle2D bounds = owner.getBounds();
      float x1 = (float) bounds.getX();
      float y1 = (float) bounds.getY();
      float x2 = (float) (bounds.getX() + bounds.getWidth());
      float y2 = (float) (bounds.getY() + bounds.getHeight());
      if ((px >= x1 - d1) && (px <= x2 + d1) && (py >= y1 - d1)
	  && (py <= y2 + d1)) {
	// inside the rect bound by the two ends
	result = PointInBounds;
	int v = onVertex(px, py);
	if (v >= 0) {
	  result = PointOnPolyShapeVertex + v;
	} else {
	  int e = onEdge(px, py);
	  if (e >= 0) {
	    if (isPolygon) {
	      result = PointOnPolyShapeEdge + e;
	    } else {
	      int n = points.size();
	      if (e < n) {
		result = PointOnPolyShapeEdge + e;
	      }
	    }
	  } else if (isPolygon) {
	    if (polygon == null) {
	      makePolygon();
	    }
	    if (polygon.contains(px, py)) {
	      result = PointInside;
	    }
	  }
	}
      }
    }
    return result;
  }

  public void invalidateBounds() {
    isBoundValid = false;
  }

  public void calculateBounds() {
    if (!isBoundValid) {
      makePolygon();
    }
    // super.calculateBounds();
  }

  public void setAttrFromXML(org.w3c.dom.Element node) {
    setAttrFromXML(node, null);
  }

  public void setAttrFromXML(org.w3c.dom.Element node, String namespaceURI) {
    // super.setAttrFromXML(node);
    if (node != null && "Points".equals(node.getLocalName())) {
      points.clear();

      if (debug) {
	System.out.println("PolyShape.setAttrFromXML(): <Points> tag");
      }
      org.w3c.dom.NodeList pnodes = node.getChildNodes();
      if (pnodes != null) {
	int pn = pnodes.getLength();
	for (int j = 0; j < pn; j++) {
	  org.w3c.dom.Node pnode = pnodes.item(j);
	  if (pnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
	      && "Point".equals(pnode.getLocalName())) {
	    org.w3c.dom.Element pe = (org.w3c.dom.Element) pnode;
	    String xs = pe.getAttribute("x");
	    String ys = pe.getAttribute("y");
	    if (debug) {
	      System.out.println("PolyShape.setAttrFromXML(): <Point x=\"" + xs + 
				 "\" y=\"" + ys + "\"/>");
	    }
	    try {
	      float x = Float.parseFloat(xs);
	      float y = Float.parseFloat(ys);
	      points.add(new Point2DFloat(x, y));
	    } catch (NumberFormatException ex) {
	    }
	  }
	}
      }

      // polygon = null;
      isBoundValid = false;
      calculateBounds();
    }
  }

  /**
   * check if (px, py) is on any of the vertices. return -1 if it is not
   * on any of the vertices return the index of vertices if on. (0 for
   * starting point)
   */
  protected int onVertex(float px, float py) {
    int result = -1;
    if ((px >= this.x - 2.0d) && (px <= this.x + 2.0d) && (py >= this.y - 2.0d)
	&& (py <= this.y + 2.0d)) {
      result = 0;
    }
    int n = points.size();
    float curx, cury;
    for (int i = 0; i < n; i++) {
      Point2D p = points.get(i);
      if (p != null) {
	curx = (float) p.getX() + this.x;
	cury = (float) p.getY() + this.y;
	if ((px >= curx - 2.0d) && (px <= curx + 2.0d) && (py >= cury - 2.0d)
	    && (py <= cury + 2.0d)) {
	  result = i + 1;
	  break;
	}
      }
    }
    return result;
  }

  protected int onEdge(float px, float py) {
    int result = -1;
    float prevx = this.x;
    float prevy = this.y;
    float curx, cury, x1, x2, y1, y2;
    int n = points.size();
    for (int i = 0; i < n; i++) {
      Point2D p = points.get(i);
      if (p != null) {
	curx = (float) p.getX() + this.x;
	cury = (float) p.getY() + this.y;
	if (curx < prevx) {
	  x1 = curx;
	  x2 = prevx;
	} else {
	  x1 = prevx;
	  x2 = curx;
	}
	if (cury < prevy) {
	  y1 = cury;
	  y2 = prevy;
	} else {
	  y1 = prevy;
	  y2 = cury;
	}
	if ((px >= x1 - 2.0d) && (px <= x2 + 2.0d) && (py >= y1 - 2.0d)
	    && (py <= y2 + 2.0d)) {
	  Line2D line = new Line2D.Float(prevx, prevy, curx, cury);
	  double dist = line.ptLineDist(px, py);
	  if (dist <= d) {
	    result = i;
	    break;
	  }
	}
	prevx = curx;
	prevy = cury;
      }
    }

    curx = this.x;
    cury = this.y;
    if (curx < prevx) {
      x1 = curx;
      x2 = prevx;
    } else {
      x1 = prevx;
      x2 = curx;
    }
    if (cury < prevy) {
      y1 = cury;
      y2 = prevy;
    } else {
      y1 = prevy;
      y2 = cury;
    }
    if ((px >= x1 - 2.0d) && (px <= x2 + 2.0d) && (py >= y1 - 2.0d)
	&& (py <= y2 + 2.0d)) {
      Line2D line = new Line2D.Float(prevx, prevy, curx, cury);
      double dist = line.ptLineDist(px, py);
      if (dist <= d) {
	result = n;
      }
    }
    return result;
  }

  protected void makePolygon() {
    polygon = null;
    if (points != null) {
      int n = points.size();
      int[] xpoints = new int[n + 1];
      int[] ypoints = new int[n + 1];
      float xmin, ymin, xmax, ymax;
      xmin = xmax = x;
      ymin = ymax = y;
      xpoints[0] = (int) x;
      ypoints[0] = (int) y;
      for (int i = 1; i <= n; i++) {
	Point2D p = points.get(i - 1);
	if (p != null) {
	  float px = (float) p.getX() + this.x;
	  float py = (float) p.getY() + this.y;
	  xpoints[i] = (int) px;
	  ypoints[i] = (int) py;
	  if (px < xmin) {
	    xmin = px;
	  }
	  if (px > xmax) {
	    xmax = px;
	  }
	  if (py < ymin) {
	    ymin = py;
	  }
	  if (py > ymax) {
	    ymax = py;
	  }
	}
      }
      isBoundValid = true;
      if (isPolygon) {
	polygon = new Polygon(xpoints, ypoints, n + 1);
      }
      if (owner != null) {
	owner.setEnds(xmin, ymin, xmax, ymax);
	// p1.setLocation(xmin, ymin);
	// p2.setLocation(xmax, ymax);
      }
    }
  }

  public void copyPointsTo(PolyShape s) {
    if (s != null) {
      s.setStart(x, y);
      int count = points.size();
      for (int i = 0; i < count; i++) {
	Point2D p = points.get(i);
	s.addPoint((float) p.getX(), (float) p.getY(), false);
      }
    }
  }

  public void reversePoints() { 
    int count = points.size();
    if (count > 0) { 
      Point2D p = points.get(count - 1);
      float newX = (float) p.getX() + x;
      float newY = (float) p.getY() + y;
      java.util.List<Point2D> newPoints = new ArrayList<Point2D>(count);
      for (int i = count - 2; i >= 0; i--) {
	p = points.get(i);
	newPoints.add(new Point2DFloat((float) p.getX() + x - newX, 
				       (float) p.getY() + y - newY));
      }
      newPoints.add(new Point2DFloat(x - newX, y - newY));

      x = newX;
      y = newY;
      points = newPoints;
    }
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder("[(" + x + "," + y + ")");
    int count = points.size();
    for (int i = 0; i < count; i++) {
      Point2D p = points.get(i);
      sb.append(" (" + (x + (float) p.getX()) + "," + (y + (float) p.getY()) + ")");
    }
    sb.append("]");
    return sb.toString();
  }

  protected DrawShape owner;

  protected boolean isPolygon;

  protected float x, y; // the starting point

  protected java.util.List<Point2D> points; // instance of Point2D

  // the rest of the points,
  // coordinates relative to the starting point

  protected Polygon polygon;

  protected boolean isBoundValid = false;

  protected static final boolean debug = false;

}
